module Kaboose
  class Task    
    MAX_ERRORS = 3

    attr_reader :options
    
    def process!
      processor.new.process!(self)
    end
    
    # Puts a failed Task back in the Queue with the lowest priority.
    # If a task fails MAX_ERRORS times, it is put in Queue 0 which is never processed.
    def enqueue_with_error(e)
      @options[:failures] = @options[:failures].to_i + 1
      @options[:errors] ||= []
      
      @options[:priority] = (@options[:failures] < MAX_ERRORS) ? 10 : 0
      Kaboose::Queue.enqueue(@options[:priority], @options.merge!({
        :action => @action,
        :errors => @options[:errors] << {:class => e.class.to_s, :message => e.message}
      }))
      
      Kaboose::Queue.log("Action '#{@action}' failed #{@options[:failures]} time(s).")
      
      # Send failure notification if a notification observer is defined
      if @options[:priority] == 0 && Kaboose::Queue.failure_notification_observer
        Kaboose::Queue.failure_notification_observer.call(self, e)
      end
    end

    # Puts actions in the Queue. Priority defaults to 5
    def self.publish(action, options={})
      priority = options[:priority] || 5
      options.merge!({:action => action})
      Kaboose::Queue.enqueue(priority, options)
    end
    
    # Creates a Task from a message
    def self.from_message(message)
      action = message.delete :action
      Task.new(action, message)
    end
    
    def initialize(action, options = {})
      @action = action
      @options = {:priority => 5}.merge(options)
    end
    
    protected
      # Infers a Processor's name from the action
      def processor
        Object.const_get(@action.to_s.camelize + 'Processor')
      end
  end
end